#include "DepthPeeling.hpp"
#include <stdio.h>
#define NOMINMAX
#include "GLee.h"
#include "gl.hpp"
#include "glu.hpp"
#include "GLSLProgramObject.h"

namespace
{
float g_white[3] = {1.0,1.0,1.0};

GLuint g_quadDisplayList;

GLuint g_frontFboId[2];
GLuint g_frontDepthTexId[2];
GLuint g_frontColorTexId[2];
GLuint g_frontColorBlenderTexId;
GLuint g_frontColorBlenderFboId;

GLSLProgramObject g_shaderFrontInit;
GLSLProgramObject g_shaderFrontPeel;
GLSLProgramObject g_shaderFrontBlend;
GLSLProgramObject g_shaderFrontFinal;

GLenum g_drawBuffers[] = {GL_COLOR_ATTACHMENT0_EXT,
GL_COLOR_ATTACHMENT1_EXT,
GL_COLOR_ATTACHMENT2_EXT,
GL_COLOR_ATTACHMENT3_EXT,
GL_COLOR_ATTACHMENT4_EXT,
GL_COLOR_ATTACHMENT5_EXT,
GL_COLOR_ATTACHMENT6_EXT
};

void InitFrontPeelingRenderTargets(DepthPeeling& p)
{
   glGenTextures(2, g_frontDepthTexId);
   glGenTextures(2, g_frontColorTexId);
   glGenFramebuffersEXT(2, g_frontFboId);

   for (int i = 0; i < 2; i++)
   {
      glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g_frontDepthTexId[i]);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT32F_NV,
         p.mW, p.mH, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

      glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g_frontColorTexId[i]);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, p.mW, p.mH,
         0, GL_RGBA, GL_FLOAT, 0);

      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_frontFboId[i]);
      glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
         GL_TEXTURE_RECTANGLE_ARB, g_frontDepthTexId[i], 0);
      glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
         GL_TEXTURE_RECTANGLE_ARB, g_frontColorTexId[i], 0);
   }

   glGenTextures(1, &g_frontColorBlenderTexId);
   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g_frontColorBlenderTexId);
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, p.mW, p.mH,
      0, GL_RGBA, GL_FLOAT, 0);

   glGenFramebuffersEXT(1, &g_frontColorBlenderFboId);
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_frontColorBlenderFboId);
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
      GL_TEXTURE_RECTANGLE_ARB, g_frontDepthTexId[0], 0);
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
      GL_TEXTURE_RECTANGLE_ARB, g_frontColorBlenderTexId, 0);
   gl::AssertGlError();
}

//--------------------------------------------------------------------------
void DeleteFrontPeelingRenderTargets()
{
   glDeleteFramebuffersEXT(2, g_frontFboId);
   glDeleteFramebuffersEXT(1, &g_frontColorBlenderFboId);
   glDeleteTextures(2, g_frontDepthTexId);
   glDeleteTextures(2, g_frontColorTexId);
   glDeleteTextures(1, &g_frontColorBlenderTexId);
}

//--------------------------------------------------------------------------
void MakeFullScreenQuad()
{
   g_quadDisplayList = glGenLists(1);
   glNewList(g_quadDisplayList, GL_COMPILE);

   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
   gluOrtho2D(0.0, 1.0, 0.0, 1.0);
   glBegin(GL_QUADS);
   {
      glVertex2f(0.0, 0.0); 
      glVertex2f(1.0, 0.0);
      glVertex2f(1.0, 1.0);
      glVertex2f(0.0, 1.0);
   }
   glEnd();
   glPopMatrix();

   glEndList();
}

}


DepthPeeling::DepthPeeling()
{
   mOpacity = .3f;
   mPasses = 4;
   mInitialized = false;
}

void DepthPeeling::resize(int w, int h)
{
   if (mW != w || mH != h)
   {
      mW = w;
      mH = h;
      if (mInitialized)
      {
         DeleteFrontPeelingRenderTargets();
         InitFrontPeelingRenderTargets(*this);
      }
   }
}


void DepthPeeling::Init()
{
   if (mInitialized) return;
   
   InitFrontPeelingRenderTargets(*this);

#define SHADER_PATH "shader\\"

   g_shaderFrontInit.attachVertexShader(SHADER_PATH "shade_vertex.glsl");
   g_shaderFrontInit.attachVertexShader(SHADER_PATH "front_peeling_init_vertex.glsl");
   g_shaderFrontInit.attachFragmentShader(SHADER_PATH "shade_fragment.glsl");
   g_shaderFrontInit.attachFragmentShader(SHADER_PATH "front_peeling_init_fragment.glsl");
   g_shaderFrontInit.link();

   g_shaderFrontPeel.attachVertexShader(SHADER_PATH "shade_vertex.glsl");
   g_shaderFrontPeel.attachVertexShader(SHADER_PATH "front_peeling_peel_vertex.glsl");
   g_shaderFrontPeel.attachFragmentShader(SHADER_PATH "shade_fragment.glsl");
   g_shaderFrontPeel.attachFragmentShader(SHADER_PATH "front_peeling_peel_fragment.glsl");
   g_shaderFrontPeel.link();

   g_shaderFrontBlend.attachVertexShader(SHADER_PATH "front_peeling_blend_vertex.glsl");
   g_shaderFrontBlend.attachFragmentShader(SHADER_PATH "front_peeling_blend_fragment.glsl");
   g_shaderFrontBlend.link();

   g_shaderFrontFinal.attachVertexShader(SHADER_PATH "front_peeling_final_vertex.glsl");
   g_shaderFrontFinal.attachFragmentShader(SHADER_PATH "front_peeling_final_fragment.glsl");
   g_shaderFrontFinal.link();

   // Allocate render targets first
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
   MakeFullScreenQuad();
   glDisable(GL_CULL_FACE);
   glDisable(GL_LIGHTING);
   glDisable(GL_NORMALIZE);

   //glGenQueries(1, &g_queryId);

   mInitialized = true;

}

void DepthPeeling::Enable()
{

}

void DepthPeeling::Disable()
{

}

void DepthPeeling::Begin()
{
   glDisable(GL_CULL_FACE);
   glDisable(GL_LIGHTING);
   glDisable(GL_NORMALIZE);
   mPass = 0;
   // ---------------------------------------------------------------------
   // 1. Initialize Min Depth Buffer
   // ---------------------------------------------------------------------

   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_frontColorBlenderFboId);
   glDrawBuffer(g_drawBuffers[0]);

   glClearColor(0, 0, 0, 1);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glEnable(GL_DEPTH_TEST);

   g_shaderFrontInit.bind();
   g_shaderFrontInit.setUniform("Alpha", (float*)&mOpacity, 1);
}

bool DepthPeeling::NextLayer()
{
   bool ok = mPass < mPasses;
   if (mPass > 0) EndLayer();
   if (mPass == 0) mPass = 1;
   if (ok)
   {
      g_shaderFrontInit.unbind();

      gl::AssertGlError();

      // ---------------------------------------------------------------------
      // 2. Depth Peeling + Blending
      // ---------------------------------------------------------------------
      int layer = mPass;

      int currId = layer % 2;
      int prevId = 1 - currId;

      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_frontFboId[currId]);
      glDrawBuffer(g_drawBuffers[0]);

      glClearColor(0, 0, 0, 0);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glDisable(GL_BLEND);
      glEnable(GL_DEPTH_TEST);

      g_shaderFrontPeel.bind();
      g_shaderFrontPeel.bindTextureRECT("DepthTex", g_frontDepthTexId[prevId], 0);
      g_shaderFrontPeel.setUniform("Alpha", (float*)&mOpacity, 1);
   }
   return ok;
}

void DepthPeeling::EndLayer()
{
   int layer = mPass;
   int currId = layer % 2;

   g_shaderFrontPeel.unbind();

   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, g_frontColorBlenderFboId);
   glDrawBuffer(g_drawBuffers[0]);

   glDisable(GL_DEPTH_TEST);
   glEnable(GL_BLEND);

   glBlendEquation(GL_FUNC_ADD);
   glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE,
      GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);

   g_shaderFrontBlend.bind();
   g_shaderFrontBlend.bindTextureRECT("TempTex", g_frontColorTexId[currId], 0);
   glCallList(g_quadDisplayList);
   g_shaderFrontBlend.unbind();

   glDisable(GL_BLEND);

   gl::AssertGlError();
   ++mPass;
}
void DepthPeeling::End()
{
   EndLayer();
   // ---------------------------------------------------------------------
   // 3. Final Pass
   // ---------------------------------------------------------------------
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
   glDrawBuffer(GL_BACK);
   glDisable(GL_DEPTH_TEST);

   g_shaderFrontFinal.bind();
   g_shaderFrontFinal.setUniform("BackgroundColor", g_white, 3);
   g_shaderFrontFinal.bindTextureRECT("ColorTex", g_frontColorBlenderTexId, 0);
   glCallList(g_quadDisplayList);
   g_shaderFrontFinal.unbind();

   mPass=0;

   gl::AssertGlError();
   glClearColor(1,1,1, 1);
}
